/* Copyright (c) 2004-2024 Peigen.info. All rights reserved. */

package info.peigen.hotpot.business.fee.core.repository.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import info.peigen.hotpot.business.account.core.domain.account.Account;
import info.peigen.hotpot.business.account.core.repository.service.AccountRepository;
import info.peigen.hotpot.business.fee.core.domain.FeeRule;
import info.peigen.hotpot.business.fee.core.domain.action.FeeConverter;
import info.peigen.hotpot.business.fee.core.repository.entity.FeeRuleEntity;
import info.peigen.hotpot.business.fee.core.repository.mapper.FeeRuleMapper;
import info.peigen.hotpot.business.fee.facade.enums.FeeProductCode;
import info.peigen.hotpot.common.core.enums.Currency;
import info.peigen.hotpot.common.core.result.ResultCode;
import info.peigen.hotpot.common.service.base.exception.BusinessException;
import info.peigen.hotpot.component.data.repository.beetlsql.service.AbstractBeetlSqlRepository;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.FieldDefaults;
import org.beetl.sql.core.query.LambdaQuery;
import org.beetl.sql.core.query.Query;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;

import java.util.List;
import java.util.Objects;

import static info.peigen.hotpot.business.fee.facade.enums.FeeMethod.Fixed_Amount;
import static info.peigen.hotpot.business.fee.facade.enums.FeeMethod.Ratio;
import static info.peigen.hotpot.common.core.enums.Currency.USD;


/**
 * <b>(FeeRuleRepository)</b>
 *
 * @author Peigen
 * @version 1.0.0
 * @since 2021/8/5
 */
@Data
@EqualsAndHashCode(callSuper = true)
@FieldDefaults(level = AccessLevel.PRIVATE)
@Component
public class FeeRuleRepository extends AbstractBeetlSqlRepository<FeeRuleMapper, FeeRuleEntity> {

    @Inject
    AccountRepository accountRepository;

    public void store(FeeRule domain) {
        FeeRuleEntity entity = FeeConverter.convertFeeRule(domain);
        saveOrUpdate(entity);
    }

    public FeeRule active(String ruleId) {
        Assert.notEmpty(ruleId, () -> new BusinessException(ResultCode.FAILURE, "收费规则Id不得为空!"));
        FeeRuleEntity entity = getMapper().lockByRuleId(ruleId);
        Assert.notNull(entity, () -> new BusinessException(ResultCode.FAILURE, "收费规则不得为空!"));

        Account account        = StrUtil.isNotEmpty(entity.getAccountId()) ? accountRepository.active(entity.getAccountId()) : null;
        Account billingAccount = StrUtil.isNotEmpty(entity.getBillingAccountId()) ? accountRepository.active(entity.getBillingAccountId()) : null;
        Account payerAccount   = StrUtil.isNotEmpty(entity.getPayerAccountId()) ? accountRepository.active(entity.getPayerAccountId()) : null;
        Account payeeAccount   = StrUtil.isNotEmpty(entity.getPayeeAccountId()) ? accountRepository.active(entity.getPayeeAccountId()) : null;
        return FeeConverter.convertFeeRule(entity)
                .setAccount(account)
                .setBillingAccount(billingAccount)
                .setPayerAccount(payerAccount)
                .setPayeeAccount(payeeAccount);
    }

    public FeeRule active(FeeRuleEntity entity) {
        Account account        = StrUtil.isNotEmpty(entity.getAccountId()) ? accountRepository.active(entity.getAccountId()) : null;
        Account billingAccount = StrUtil.isNotEmpty(entity.getBillingAccountId()) ? accountRepository.active(entity.getBillingAccountId()) : null;
        Account payerAccount   = StrUtil.isNotEmpty(entity.getPayerAccountId()) ? accountRepository.active(entity.getPayerAccountId()) : null;
        Account payeeAccount   = StrUtil.isNotEmpty(entity.getPayeeAccountId()) ? accountRepository.active(entity.getPayeeAccountId()) : null;
        return FeeConverter.convertFeeRule(entity)
                .setAccount(account)
                .setBillingAccount(billingAccount)
                .setPayerAccount(payerAccount)
                .setPayeeAccount(payeeAccount);
    }

    public FeeRule active(Account account, FeeProductCode productCode) {
        FeeRuleEntity entity = null;
        if (ObjectUtil.isNotNull(account)) {
            entity = getMapper().findByAccountIdAndProductCode(account.getAccountId(), productCode);
        }

        if (ObjectUtil.isNotNull(entity)) {
            Account billingAccount = StrUtil.isNotEmpty(entity.getBillingAccountId()) ? accountRepository.active(entity.getBillingAccountId()) : null;
            Account payerAccount   = StrUtil.isNotEmpty(entity.getPayerAccountId()) ? accountRepository.active(entity.getPayerAccountId()) : null;
            Account payeeAccount   = StrUtil.isNotEmpty(entity.getPayeeAccountId()) ? accountRepository.active(entity.getPayeeAccountId()) : null;
            return FeeConverter.convertFeeRule(entity)
                    .setAccount(account)
                    .setBillingAccount(billingAccount)
                    .setPayerAccount(payerAccount)
                    .setPayeeAccount(payeeAccount);
        }
        return defaultRule(account, productCode);
    }

    /**
     * 默认收费账户
     * 根据币种来生成，一劳永逸
     *
     * @param account 收费计算客户
     * @param code    产品码
     *
     * @return FeeRule
     */
    public FeeRule defaultRule(Account account, FeeProductCode code) {
        Account feeAccount = accountRepository.active(account.getAccountId());
        // 优先从数据库查
        FeeRule rule = active(null, code);
        if (Objects.nonNull(rule)) {
            rule.setPayeeAccount(feeAccount);
            rule.setAccount(account);
            return rule;
        }

        rule = FeeRule.builder()
                .code(code)
                .account(account)
                .billingAccount(account)
                .payerAccount(account)
                .payeeAccount(feeAccount)
                .build();

        return switch (code) {
            case Deposit -> rule.setMethod(Fixed_Amount)
                    .setContext("18.00")
                    .setCurrency(USD);
            case Transfer -> rule.setMethod(Fixed_Amount)
                    .setContext("20.00")
                    .setCurrency(USD);
            case Withdraw -> rule.setMethod(Ratio)
                    .setContext("0.004");
            case Settlement -> rule.setMethod(Fixed_Amount)
                    .setContext("0.00");
            default -> rule;
        };
    }

    public FeeRuleEntity queryFeeRule(String stationParentId, String partnerId, String accountId, FeeProductCode code, Currency currency) {
        boolean             partnerRule = StrUtil.isAllEmpty(stationParentId, accountId);
        List<FeeRuleEntity> resultList;
        if (!partnerRule) {
            // 优先查针对账户的费率
            resultList = getAccountRule(code, currency, partnerId, accountId);
            if (CollUtil.isNotEmpty(resultList)) {
                return resultList.stream().findFirst().orElse(null);
            }
            // 其次查针对站点的费率
            resultList = getStationRule(code, currency, partnerId, stationParentId);
            if (CollUtil.isNotEmpty(resultList)) {
                return resultList.stream().findFirst().orElse(null);
            }
            // 默认规则
            resultList = getDefaultFeeRule(code, currency, partnerId);
            Assert.isTrue(resultList.size() == 1, () -> new BusinessException(ResultCode.FAILURE, "查询收费规则失败![code=" + code + "]"));
            return resultList.stream().findFirst().orElse(null);
        } else {
            resultList = getDefaultFeeRule(code, currency, partnerId);
            Assert.isTrue(resultList.size() == 1, () -> new BusinessException(ResultCode.FAILURE, "查询默认收费规则失败![code=" + code + "]"));
            return resultList.stream().findFirst().orElse(null);
        }
    }

    private List<FeeRuleEntity> getDefaultFeeRule(FeeProductCode code, Currency currency, String partnerId) {
        LambdaQuery<FeeRuleEntity> defaultQuery = getMapper().createLambdaQuery()
                .andEq(FeeRuleEntity::getCode, Query.filterNull(code))
                .andEq(FeeRuleEntity::getCurrency, Query.filterNull(currency))
                .andEq(FeeRuleEntity::getPartnerId, Query.filterNull(partnerId))
                .andIsNull(FeeRuleEntity::getStationParentId)
                .andIsNull(FeeRuleEntity::getAccountId);
        return defaultQuery.select();
    }

    private List<FeeRuleEntity> getStationRule(FeeProductCode code, Currency currency, String partnerId, String stationParentId) {
        LambdaQuery<FeeRuleEntity> query = getMapper().createLambdaQuery()
                .andEq(FeeRuleEntity::getCode, Query.filterNull(code))
                .andEq(FeeRuleEntity::getCurrency, Query.filterNull(currency))
                .andEq(FeeRuleEntity::getStationParentId, Query.filterNull(stationParentId))
                .andEq(FeeRuleEntity::getPartnerId, Query.filterNull(partnerId));
        return query.select();
    }

    private List<FeeRuleEntity> getAccountRule(FeeProductCode code, Currency currency, String partnerId, String accountId) {
        LambdaQuery<FeeRuleEntity> query = getMapper().createLambdaQuery()
                .andEq(FeeRuleEntity::getCode, Query.filterNull(code))
                .andEq(FeeRuleEntity::getCurrency, Query.filterNull(currency))
                .andEq(FeeRuleEntity::getAccountId, Query.filterNull(accountId))
                .andEq(FeeRuleEntity::getPartnerId, Query.filterNull(partnerId));
        return query.select();
    }

    public List<FeeRuleEntity> listByPartnerIds(List<String> partnerIds, FeeProductCode code) {
        LambdaQuery<FeeRuleEntity> query = getMapper().createLambdaQuery()
                .andEq(FeeRuleEntity::getCode, Query.filterNull(code))
                .andIn(FeeRuleEntity::getPartnerId, Query.filterNull(partnerIds));
        return query.select();
    }
}